/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit.handlers;

import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getFragments;
import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getOperands;
import static java.lang.Math.max;
import static java.lang.Math.min;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.uml2.uml.CombinedFragment;
import org.eclipse.uml2.uml.Continuation;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.InteractionFragment;
import org.eclipse.uml2.uml.InteractionOperand;
import org.eclipse.uml2.uml.Lifeline;

import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.AbstractLinkEditPart;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseContainedFragmentsCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseCoveredCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.AnalyseOwnerCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.CaptureCoveredFragmentsCommand;
import com.cea.papyrus.diagram.sequence.sedi.edit.command.DecideCFBoundsCommand;
import com.cea.papyrus.diagram.sequence.sedi.figures.SeDiAnchor;
import com.cea.papyrus.diagram.sequence.sedi.model.command.CoverCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.DelegatingCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.ReleaseContainedFragmentsCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.SetConstraintCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.command.SetOwnerCommand;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Handles automatic layout of CombinedFragments.
 * 
 * @author frankd@ifi.uio.no
 */
public class CombinedFragmentLayoutHandler implements LayoutHandler<CombinedFragment> {
    @SuppressWarnings("unchecked")
    public Rectangle layout(Diagram diagram, CombinedFragment cf) {
        // the left side should be slightly to the left of the leftmost
        // covered lifeline, and the right side slighly to the right of the
        // rightmost covered lifeline.
        // the top aside should be slightly below the lowest preceeding item
        // that cover any of the of covered lifelines, and correspondingly
        // for the bottom (slightly above)

        int left = Integer.MAX_VALUE; // , top = left;
        int right = Integer.MIN_VALUE; // , bottom = right;
        for (Lifeline lifeline : (List<Lifeline>) cf.getCovereds()) {
            GraphNode llview = ModelUtil.getGraphNode(lifeline, diagram);
            Rectangle bounds = new Rectangle(llview.getPosition(), llview.getSize());
            left = min(left, bounds.x);
            right = max(right, bounds.right());

            // TODO calculate top and bottom positions
            /*
             * int iuIndex = lifeline.getCoveredBys().indexOf(iu); if (iuIndex > 0) {
             * InteractionFragment fragment = (InteractionFragment)
             * lifeline.getCoveredBys().get(iuIndex - 1); if (fragment instanceof
             * EventOccurrence) { EventOccurrence eo = (EventOccurrence) fragment;
             * NamedElement owner = MessageUtil.getOwner(eo); diagram.getViewFor(owner)
             * eo.getReceiveMessage() } }
             */
        }

        return new Rectangle(left, 100, right - left, 50);
    }

    @SuppressWarnings("unchecked")
    public Command[] getMoveCommands(final EditPart fragmentPart, final Rectangle suggestedBounds) {

        // Bounds are expected to be in diagram coordinates

        final List<Command> cmds = new LinkedList<Command>();

        final Map<Object, EditPart> editPartRegistry =
                (Map<Object, EditPart>) fragmentPart.getViewer().getEditPartRegistry();

        final GraphNode fragmentView = (GraphNode) fragmentPart.getModel();

        final IFigure fragmentFigure = ((GraphicalEditPart) fragmentPart).getFigure();

        // TODO Update doc
        // DecideCFBoundsCommand [SeDi]
        // Ensure that the new CF is properly contained within its owner.
        // SetBoundsHintCommand [SeDi]
        // Registers a Bounds hint that can be used when the
        // SeDi GraphcialElement is created.
        // SetConstraintCommand
        // Move the lifeline
        // AnalyseCoveredByCommand
        // CoveredByCommand
        // Moving the fragment may have brought a different set of
        // lifelines beneath it.

        // We don't really know to begin with who the owner will be, so
        // let's assume that the requested bounds are granted and see who
        // the owner then will be. This owner will then influcence the
        // CFs bounds. However, the owner might change due to decisions
        // made by the CFBoundsCommand, so we must analyse again to verify
        // that the owner is still valid.

        // The bounds are in diagram coordinates, so ask parent (= the diagram)
        // for the absolute coordinates
        fragmentFigure.getParent().translateToAbsolute(suggestedBounds);

        final AnalyseOwnerCommand preOwnerCmd = new AnalyseOwnerCommand();
        preOwnerCmd.setLocation(suggestedBounds.getLocation());
        final Diagram diagram = LookForElement.getDiagram(fragmentView);
        preOwnerCmd.setDiagram(diagram);
        preOwnerCmd.setAnyEditPart(fragmentPart);

        // NOTE CombinedFragments are added globally, so the bounds
        // hint must be converted to absolute coordinates
        final DecideCFBoundsCommand decideBoundsCmd = new DecideCFBoundsCommand();
        decideBoundsCmd.setDiagram(diagram);
        decideBoundsCmd.setHint(suggestedBounds);
        decideBoundsCmd.setAnyEditPart(fragmentPart);
        // glue: setOwner

        final AnalyseOwnerCommand ownerCmd = new AnalyseOwnerCommand();
        ownerCmd.setDiagram(diagram);
        ownerCmd.setAnyEditPart(fragmentPart);
        // glue: setLocation

        final SetOwnerCommand setOwnerCmd = new SetOwnerCommand();
        setOwnerCmd.setFragment(ModelUtil.<InteractionFragment> getSemantic(fragmentView));
        // glue: owner

        // final SetBoundsHintCommand hintCmd = new SetBoundsHintCommand();
        // final Rectangle preferredBounds =
        // fragmentFigure.getPreferredBounds(suggestedBounds);
        final SetConstraintCommand boundsCmd = new SetConstraintCommand();
        boundsCmd.setElement(fragmentView);
        // glue: setBounds

        // Step 2
        final AnalyseCoveredCommand analyseCoveredCmd = new AnalyseCoveredCommand();
        analyseCoveredCmd.setEditPart(fragmentPart);
        analyseCoveredCmd.setFragmentView(fragmentView);

        final CoverCommand coverCmd = new CoverCommand();
        coverCmd.setFragment(ModelUtil.<InteractionFragment> getSemantic(fragmentView));

        final ReleaseContainedFragmentsCommand releaseCmd = new ReleaseContainedFragmentsCommand();
        releaseCmd.setCombinedFragment(ModelUtil.<CombinedFragment> getSemantic(fragmentView));

        final AnalyseContainedFragmentsCommand analyseCaptureCmd =
                new AnalyseContainedFragmentsCommand();
        analyseCaptureCmd.setAnyEditPart(fragmentPart);
        analyseCaptureCmd.setCombinedFragmentView(fragmentView);

        final CaptureCoveredFragmentsCommand captureCmd = new CaptureCoveredFragmentsCommand();
        // glue: Capture

        Command glue1 = new Command() {
            public void execute() {
                InteractionFragment owner = preOwnerCmd.getOwner();
                decideBoundsCmd.setOwner(getRealOwner(owner, ModelUtil
                        .<CombinedFragment> getSemantic(fragmentView)));
            }
        };

        Command glue2 = new Command() {
            public void execute() {
                Rectangle prefBounds = decideBoundsCmd.getPreferredBounds();
                ownerCmd.setLocation(prefBounds.getLocation());

                GraphicalEditPart diagramEditPart =
                        (GraphicalEditPart) editPartRegistry.get(diagram);
                diagramEditPart.getFigure().translateToRelative(prefBounds);

                boundsCmd.setBounds(prefBounds);
            }
        };

        Command glue3 = new Command() {
            public void execute() {
                InteractionFragment owner = ownerCmd.getOwner();
                setOwnerCmd.setOwner(getRealOwner(owner, ModelUtil
                        .<CombinedFragment> getSemantic(fragmentView)));
            }
        };

        Command glue4 = new Command() {
            public void execute() {
                coverCmd.setCovered(analyseCoveredCmd.getCovered());
            }
        };

        Command glue5 = new Command() {
            public void execute() {
                captureCmd.setCapture(analyseCaptureCmd.getContained());
            }
        };

        cmds.add(preOwnerCmd);
        cmds.add(glue1);
        cmds.add(decideBoundsCmd);
        cmds.add(glue2);
        cmds.add(ownerCmd);
        cmds.add(boundsCmd);
        cmds.add(glue3);
        cmds.add(setOwnerCmd);
        cmds.add(analyseCoveredCmd);
        cmds.add(glue4);
        cmds.add(coverCmd);
        cmds.add(releaseCmd);
        cmds.add(analyseCaptureCmd);
        cmds.add(glue5);
        cmds.add(captureCmd);

        // Also move all continuations
        final DelegatingCommand moveContsCmd = new DelegatingCommand();
        final Rectangle curCfBounds =
                new Rectangle(fragmentView.getPosition(), fragmentView.getSize());
        Command prepareMoveContsCmd = new Command() {
            public void execute() {
                CompoundCommand cmds = new CompoundCommand();

                Rectangle prefCfBounds = decideBoundsCmd.getPreferredBounds();
                Point moveDelta =
                        new Point(prefCfBounds.x - curCfBounds.x, prefCfBounds.y - curCfBounds.y);
                Dimension sizeDelta = new Dimension(0, 0);

                final Diagram diagram = LookForElement.getDiagram(fragmentView);
                final CombinedFragment cfragment =
                        ModelUtil.<CombinedFragment> getSemantic(fragmentView);
                for (InteractionOperand operand : getOperands(cfragment)) {
                    for (InteractionFragment fragment : getFragments(operand)) {
                        if (fragment instanceof Continuation) {
                            GraphNode contView = ModelUtil.getGraphNode(fragment, diagram);
                            EditPart contEP = editPartRegistry.get(contView);

                            Rectangle currentBounds =
                                    new Rectangle(contView.getPosition(), contView.getSize());
                            Point moveTo =
                                    new Point(currentBounds.x + moveDelta.x, currentBounds.y
                                            + moveDelta.y);

                            ChangeBoundsRequest moveRequest = new ChangeBoundsRequest();
                            moveRequest.setType(RequestConstants.REQ_MOVE);
                            moveRequest.setEditParts(contEP);
                            moveRequest.setLocation(moveTo);
                            moveRequest.setMoveDelta(moveDelta);
                            moveRequest.setSizeDelta(sizeDelta);

                            Command moveCmd = contEP.getCommand(moveRequest);
                            if (moveCmd != null)
                                cmds.add(moveCmd);

                        }
                    }
                }
                moveContsCmd.setCommand(cmds);
            }
        };
        cmds.add(prepareMoveContsCmd);
        cmds.add(moveContsCmd);

        // update IO.
        Command updateInteractionOperator = new Command() {
            /**
             * @see org.eclipse.gef.commands.Command#execute()
             */
            @Override
            public void execute() {
                for (InteractionOperand interactionOperand : ModelUtil
                        .<CombinedFragment> getSemantic(fragmentView).getOperands()) {
                    GraphEdge view = ModelUtil.getGraphEdge(interactionOperand, diagram);
                    if (view != null) {
                        AbstractLinkEditPart linkEditPart =
                                (AbstractLinkEditPart) fragmentPart.getViewer()
                                        .getEditPartRegistry().get(view);
                        if (linkEditPart != null) {
                            PolylineConnection polylineConnection =
                                    (PolylineConnection) linkEditPart.getFigure();
                            view.getAnchor().get(0).setPosition(
                                    new Point(((SeDiAnchor) polylineConnection.getSourceAnchor())
                                            .getOffsetX(), ((SeDiAnchor) polylineConnection
                                            .getSourceAnchor()).getOffsetY()));
                            view.getAnchor().get(1).setPosition(
                                    new Point(((SeDiAnchor) polylineConnection.getTargetAnchor())
                                            .getOffsetX(), ((SeDiAnchor) polylineConnection
                                            .getTargetAnchor()).getOffsetY()));
                        }
                    }
                }

            }
        };
        cmds.add(updateInteractionOperator);

        return cmds.toArray(new Command[cmds.size()]);
    }

    private InteractionFragment getRealOwner(InteractionFragment owner,
            CombinedFragment enclosingCfragment) {

        // The detected owner might be the operand itself.
        // If so, we want the CF's owner.
        if (owner instanceof InteractionOperand) {
            Element cf = owner.getOwner();
            if (cf == enclosingCfragment)
                owner = (InteractionFragment) cf.getOwner();
        }
        return owner;
    }
}
